﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
#if !DOT_NET
using Windows.Foundation.Collections;

#endif

namespace StyleMVVM.Data.Activation
{
	public sealed class CompiledDeepCloneStrategy : IDeepCloneStrategy
	{
		private static readonly string supplementalString = typeof(CompiledDeepCloneStrategy).FullName;
		private static readonly MethodInfo deepCloneIntoInstanceMethod;
		private Action<IActivationService, object, object> cloneAction;
		private readonly Type cloneType;

		static CompiledDeepCloneStrategy()
		{
			deepCloneIntoInstanceMethod =
				typeof(IActivationService).GetTypeInfo().GetDeclaredMethod("DeepCloneIntoInstance");
		}

		public CompiledDeepCloneStrategy(Type cloneType)
		{
			this.cloneType = cloneType;
		}

		public void Initialize()
		{
			internalInitialize();
		}

		public IActivationService ActivationService { get; set; }

		public object DeepClone(object tValue, object tCloneInstance)
		{
			if (cloneAction != null)
			{
				cloneAction(ActivationService, tValue, tCloneInstance);
			}

			return tCloneInstance;
		}

		private void internalInitialize()
		{
			cloneAction = createNonContainerCloneMethod();
		}

		private Action<IActivationService, object, object> createNonContainerCloneMethod()
		{
			ParameterExpression activationService =
				Expression.Parameter(typeof(IActivationService), "activationService");
			ParameterExpression oValue = Expression.Parameter(typeof(object), "oValue");
			ParameterExpression oClone = Expression.Parameter(typeof(object), "oClone");

			ParameterExpression tValue = Expression.Variable(cloneType, "tValue");
			ParameterExpression tClone = Expression.Variable(cloneType, "tClone");

			Expression assignTValue =
				Expression.Assign(tValue, Expression.Convert(oValue, cloneType));
			Expression assignTClone =
				Expression.Assign(tClone, Expression.Convert(oClone, cloneType));

			List<Expression> cloneStatements =
				new List<Expression> { assignTValue, assignTClone };

			foreach (PropertyInfo declaredProperty
				in cloneType.GetTypeInfo().DeclaredProperties)
			{
				if (declaredProperty.CanWrite)
				{
					bool skip = false;

					foreach (Attribute attr
						in declaredProperty.GetCustomAttributes())
					{
						var skipCloningAttribute = attr as ISkipCloningAttribute;

						if (skipCloningAttribute != null)
						{
							skip = true;
							break;
						}
					}

					if (skip)
					{
						break;
					}

					Type declaringType =
						declaredProperty.PropertyType;
					TypeInfo declaringInfo =
						declaringType.GetTypeInfo();

					if (declaringInfo.IsPrimitive ||
					    declaringType == typeof(string))
					{
						Expression assignExpression =
							Expression.Assign(
								Expression.Property(tClone, declaredProperty),
								Expression.Property(tValue, declaredProperty));

						cloneStatements.Add(assignExpression);
					}
					else
					{
						Expression createCloneCall =
							Expression.Call(activationService,
							                deepCloneIntoInstanceMethod,
							                Expression.Property(tValue, declaredProperty),
							                Expression.Property(tClone, declaredProperty));

						Expression assignExpression =
							Expression.Assign(
								Expression.Property(tClone, declaredProperty),
								Expression.Convert(createCloneCall, declaringType));

						// If reference type test for null befor calling clone
						if (!declaringInfo.IsValueType)
						{
							Expression notNullTest =
								Expression.IsFalse(
									Expression.Equal(
										Expression.Property(tValue, declaredProperty),
										Expression.Constant(null)));

							Expression ifNotNullAssign =
								Expression.IfThen(notNullTest, assignExpression);

							cloneStatements.Add(ifNotNullAssign);
						}
						else
						{
							cloneStatements.Add(assignExpression);
						}
					}
				}
			}

			BlockExpression cloneblock =
				Expression.Block(new[] { tValue, tClone }, cloneStatements);

			try
			{
				return Expression.Lambda<Action<IActivationService, object, object>>(
					cloneblock, activationService, oValue, oClone).Compile();
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while compiling DeepCloneStrategy",
				             supplementalString,
				             exp);
			}

			return null;
		}
	}
}